#!/usr/bin/python3
#-*-coding:utf-8-*-

###############################################################
## Name     : gen_tb
## Author   : gaojiaming
## Date     : Tue Nov 29 22:26:14 CST 2022
## Description:
##
##
###############################################################

import sys
import os
import re
import argparse
import difflib

###############################################################
## RTL的class 和 agent的class
###############################################################
class Agent:#{{{
  itf_name = "_interface"
  type_name = "_agent"
  def __init__(self, name, inst, itf_path = ""):
    self.name  = name
    self.inst  = inst
    self.itf_path  = itf_path
    self.port_list  = []
    self.inst_list  = []
    self.inst_port_list = []
  pass
  def get_port(self):
    with open(self.itf_path, "r") as hd:
      handle = hd.readlines()
      for line in handle:
        res = re.search("^\s*logic(.*);$", line)
        if res:
          port = res.group(1).strip()
          port = re.sub(r"\[.*\]", "", port)
          port = port.strip()
          self.port_list.append(port)
  pass
  def get_inst(self):
    res = re.search(r"(\w+)\[(\d+)\]", self.inst)
    if res:
      inst_name = res.group(1)
      inst_mun  = int(res.group(2))
      for i in range(inst_mun):
        inst = inst_name + "[" + i + "]"
        self.inst_list.append(inst)
    else:
      self.inst_list.append(self.inst)
  pass
  def get_inst_port(self):
    self.get_port()
    self.get_inst()
    for inst in self.inst_list:
      for port in self.port_list:
        self.inst_port_list.append(inst + "." + port)
  pass    
pass #}}}

class Module:#{{{
  def __init__(self, name, inst, path):
    self.name  = name
    self.inst  = inst
    self.path  = path
    self.port_list = []
    self.para_list = []
  pass

  def get_port_param(self):
    def del_note_code(handle):#{{{
      ret_handle = []
      del_flag = 0
      for line in handle:
        re_s  = re.search(r"^\/\*", line)
        re_e  = re.search(r"\*\/", line)
        re_se = re.search(r"^\/\*.*\*\/$", line)
        line  = re.sub(r"\/\/.*$", "", line)
        if re_se:
          continue
        elif re_s:
          del_flag = 1
        elif re_e:
          del_flag = 0
          continue

        if del_flag != 1:
          ret_handle.append(line)

      return ret_handle
    pass #}}}
    def width_space_ex(matched): #{{{
      return " " + matched.group(1) + " "
    pass #}}}
    with open (self.path, "r") as rtl:
      handle = rtl.readlines()
      handle = del_note_code(handle)
      module= "none"
      for line in handle:
        line = line.strip()
        line = re.sub("(\[.*\])", width_space_ex, line)
        re0 = re.match(r"module\s+(\w+)", line)
        re1 = re.search(r"^\s*(input|output|wire|reg)(\s+wire|\s+reg)?\s+(\[.*\])?\s*([\s,\w]+)\s*", line)
        re2 = re.search(r"^\s*(parameter)\s+(\w+)\s*=\s*([\$\(\)\w\']+)", line)
        type = "wire"
        port = "none"
        width = ""
        if re0:
          module = re0.group(1)
        if re1:
          if re1.group(3):
            width = re1.group(3)
          if re.match(r"input|output", re1.group(1)):
            port = re1.group(1).strip()
          else:
            type = re1.group(1).strip()
          if re1.group(2):
            type = re1.group(2).strip()
          
          for sig in re1.group(4).split(r"\s*,\s*"):
            name = sig.strip(",")
            if port == "input" or port == "output":
              self.port_list.append(name)
        if re2:
          param = re2.group(2)
          self.para_list.append(param)
  pass
pass #}}}

###############################################################
## 函数
###############################################################
def input_cfg_file():#{{{
  '''
  输入参数需要两个：
  1. sim filelist，必须包括agent和rtl
  2. gen_tb.cfg，需要通过这个来找rtl和agent以及agent例化名
  3. link.sv，输出文件路径
  '''
  if len(sys.argv) <= 3:
    print("ERROR: You need input a agent name")
    sys.exit()
  filelist = sys.argv[1]
  cfg_file = sys.argv[2]
  outp_path= sys.argv[3]

  filelist_real_path = os.path.realpath(filelist)

  if not os.path.exists(cfg_file):
    print("ERROR: You need input a cfg_file")
    sys.exit()
  return filelist_real_path, cfg_file, outp_path
#}}}

def replace_env_var(path): #{{{
  '''
  把环境变量替换为实际路径
  '''
  def get_env_nobracket(match): # MVU_ROOT
    return os.getenv(match.group(1))
  pass
  def get_env_bracket(match):
    env = match.group(1) # {MVU_ROOT}
    env = re.sub(r"\{|\}", "", env)
    return os.getenv(env)
  pass
  
  path = re.sub(r"\$(\w+)", get_env_nobracket, path)
  path = re.sub(r"\$(\{\w+\})", get_env_bracket, path)
  return path
pass #}}}

def replace_relative_path(base, offset=""): #{{{
  '''
  把相对路径替换为绝对路径
  '''
  #print(base, offset)
  if os.path.isabs(offset): #是绝对路径
    return offset
  elif offset != "":
    path = os.path.join(base, offset)
  else:
    path = base
  return os.path.realpath(path)
pass #}}}

def process_list(base_path, lst): #{{{
  '''
  解析filelist，base_path应该是sim目录或者和sim同层目录
  '''
  file_list = []
  lst = lst.strip()
  lst = replace_relative_path(base_path, replace_env_var(lst))
  #print("base_path is %s" % base_path)
  #file_list.append(lst)
  (filepath, filename) = os.path.split(lst)
  with open (lst, "r") as handle:
    for line in handle:
      line = re.sub(r"\/\/.*", "", line)
      #if line != "": #
      if not re.match("^\s*$", line):
        re0 = re.match(r"\s*\+incdir\+(.*)", line)
        re1 = re.match(r"\s*\-f\s+(.*)", line)
        re2 = re.match(r"\s*-v", line)
        re3 = re.match(r"\s*-y", line)
        if re0:
          path = replace_relative_path(base_path, replace_env_var(re0.group(1)))
          #print("in re0, path = %s" % path)
          file_list.append(path) #这是一个全局检索路径
        elif re1:
          path = replace_relative_path(base_path, replace_env_var(re1.group(1)))
          #print("in re1, path = %s" % path)
          file_list.extend(process_list(base_path, path))
        elif not re2 and not re3: # ../../mvu_sim_define.sv
          #print("in re2, path = %s" % line.strip())
          path = line.strip()
          path = replace_relative_path(base_path, replace_env_var(path))
          #print("in re2, path = %s" % path)
          file_list.append(path)
  return file_list
pass #}}}

def process_list_top(lst): #{{{
  lst = replace_relative_path(replace_env_var(lst))
  base_path, list_name = os.path.split(lst)
  file_lst = process_list(base_path, list_name) #这个输出是一个可以编译的list，而不是纯文件
  return file_lst
pass#}}}

def sys_cfg_file(cfg_file, file_lst): #{{{
  '''
  遍历配置文件，找到agent，进一步找到interface.sv
  '''
  agent_list = []
  with open(cfg_file, "r") as hd:
    handle = hd.readlines()
    for line in handle:
      if not re.search(r"^\s*//", line):
        arr = line.split("|")
        parent = arr[0].strip()
        name   = arr[1].strip()
        inst   = arr[2].strip()
        type   = arr[3].strip()
        create = arr[4].strip()
        connect= arr[5].strip()
        if re.search("AGENT", type): #找agent
          #print(name)
          agent_itf_path = re.sub("{}".format(Agent.type_name), "", name) + Agent.itf_name + ".sv"
          for path in file_lst:
            if re.search(agent_itf_path, path):
              #print(name, path)
              agent = Agent(name, inst, path)
              agent.get_inst_port()
              agent_list.append(agent)
              break
        if re.search("RTL", type): #找rtl
          rtl_path = name + ".v"
          find = 0
          for path in file_lst:
            if re.search(rtl_path, path):
              rtl = Module(name, inst, path)
              rtl.get_port_param()
              find = 1
              break
          
          if find == 0:
            rtl_path = name + ".sv"
          for path in file_lst:
            if re.search(rtl_path, path):
              rtl = Module(name, inst, path)
              rtl.get_port_param()
              find = 1
              break
          if find == 0:
            print("ERROR: Can not find top module path")
            sys.exit()
  return agent_list, rtl
pass #}}}

def similar_diff_ratio(str1, str2): #{{{
  set1 = set(str1)
  set2 = set(str2)
  common_ch = set1.intersection(set2)
  diff_ratio = difflib.SequenceMatcher(None, str1, str2).ratio()
  num_ratio  = float(len(common_ch)/(len(str1)+len(str2)))
  return diff_ratio, num_ratio
pass #}}}

def find_similar_port(src_port_list:list, dst_port_list:list):
  src_similar_dst = {}
  for src_port in src_port_list:
    diff_ratio = 0.0
    num_ratio  = 0.0
    ratio      = 0.0
    find_dst_port  = src_port
    for dst_port in dst_port_list:
      now_diff_ratio, now_num_ratio = similar_diff_ratio(src_port, dst_port)
      now_ratio = now_diff_ratio + now_num_ratio
      
      if now_diff_ratio > diff_ratio:
        diff_ratio = now_diff_ratio
        num_ratio = now_num_ratio
        find_dst_port = dst_port

      ratio = diff_ratio + num_ratio
      src_similar_dst[src_port] = find_dst_port
  return src_similar_dst
pass #}}}

def find_similar_port_top(agent_list, rtl): #{{{
  rtl_similar_dict = {}
  agent_similar_dict = {}
  similar_dict = {}

  similar_dict["clk"] = "clk"
  similar_dict["rst_n"] = "rst_n"

  agent_port_list = []
  rtl_port_list = rtl.port_list
  for agent in agent_list:
    agent_port_list.extend(agent.inst_port_list)
  
  remain_num = -1
  while True:
    rtl_similar_dict = find_similar_port(rtl_port_list, agent_port_list)
    agent_similar_dict = find_similar_port(agent_port_list, rtl_port_list)

    for rtl_port in rtl_similar_dict.keys():
      if rtl_port == "clk" or rtl_port == "rst_n":
        continue
      agent_port = rtl_similar_dict[rtl_port]
      agent_port_sim_rtl_port = agent_similar_dict[agent_port]
      if rtl_port == agent_port_sim_rtl_port:
        similar_dict[rtl_port] = agent_port
    
    for rtl_port in similar_dict.keys():
      if rtl_port in rtl_similar_dict:
        if rtl_port == "clk" or rtl_port == "rst_n":
          pass
        else:
          agent_port = rtl_similar_dict[rtl_port]
          del agent_similar_dict[agent_port]
        del rtl_similar_dict[rtl_port]
    
    rtl_port_list = list(rtl_similar_dict.keys())
    agent_port_list = list(agent_similar_dict.keys())
    
    if len(rtl_port_list) == remain_num:
      break
    else:
      remain_num = len(rtl_port_list)
    print("rtl_port_list num: ", len(rtl_port_list))
  
  return similar_dict
pass #}}}

def gen_rtl_inst(rtl, similar_dict): #{{{
  ret = ""
  ret += "{0} #(\n".format(rtl.name)
  for para in rtl.para_list:
    ret += "  .{0}({0}),\n".format(para)
  ret = re.sub(r",\n$", "\n", ret)
  ret += ") {0}(\n".format(rtl.inst)
  for port in rtl.port_list:
    link = "/*cannot link*/"
    if port in similar_dict:
      link = similar_dict[port]
    ret += "  .{0}({1}),\n".format(port, link)
  ret = re.sub(r",\n$", "\n", ret)
  ret += ");\n"
  return ret
pass #}}}

###############################################################
## 主函数
###############################################################
def main(): #{{{
  filelist, cfg_file, outp_path = input_cfg_file()
  file_lst = process_list_top(filelist)
  agent_list, rtl = sys_cfg_file(cfg_file, file_lst)
  similar_dict = find_similar_port_top(agent_list, rtl)
  outp = gen_rtl_inst(rtl, similar_dict)

  with open(outp_path, "w") as hd:
    hd.write(outp)
pass #}}}

if __name__ == "__main__": #{{{
  print("%s is working" % __file__)
  main()
#}}}
